package com.owen.util;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.owen.annotation.ExcelHead;
import org.apache.poi.ss.usermodel.*;
import org.springframework.util.Assert;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * Excel相关工具类
 */
public class MyExcelUtil {

    private static ExcelWriter writer;
    private static Integer columnNum = 0;

    /**
     * 获取writer;
     *
     * @param isXlsx 是否为xlsx
     * @return ExcelWriter
     */
    public static ExcelWriter getWriter(Boolean isXlsx) {
        return cn.hutool.poi.excel.ExcelUtil.getWriter(isXlsx);
    }

    /**
     * 导出excel
     *
     * @param data        数据
     * @param headerClass header标题
     * @param response    输出对像
     * @param exportName  导出名称
     * @param <T>         泛型
     */
    public static <T> void exportExcel(Class<T> headerClass, List<T> data, String exportName, HttpServletResponse response) {
        writer = getWriter(true);
        handleHeader(headerClass);
        write(exportName, data, response);
    }


    /**
     * 导出excel
     *
     * @param data        数据
     * @param headerClass header标题
     * @param response    response输出对像
     * @param fileName    导出文件名称
     * @param <T>         泛型
     */
    public static <T> void exportExcel(Class<T> headerClass, List<T> data, String fileName, HttpServletResponse response, Consumer<ExcelWriter> consumer) {
        writer = getWriter(true);
        handleHeader(headerClass);
        handleData(data);
        consumer.accept(writer);
        write(fileName, null, response);
    }

    /**
     * 导出excel
     *
     * @param data        数据
     * @param headerClass header标题
     * @param <T>         泛型
     */
    public static <T> void exportExcel(List<T> data, Class<T> headerClass, String url) {
        writer = getWriter(true);
        handleHeader(headerClass);
        writeToDesk(data, url);
    }

    /**
     * 导出excel
     *
     * @param data        数据
     * @param headerClass header标题
     * @param consumer    回调
     * @param <T>         泛型
     */
    public static <T> void exportExcel(List<T> data, Class<T> headerClass, String url, Consumer<ExcelWriter> consumer) {
        writer = getWriter(true);
        handleHeader(headerClass);
        consumer.accept(writer);
        writeToDesk(data, url);
    }

    /**
     * 处理数据表格
     *
     * @param data 数据
     * @param <T>  泛型
     */
    private static <T> void handleData(List<T> data) {
        if (ObjectUtil.isEmpty(data)) {
            return;
        }
        writer.write(data, true);
        Sheet sheet = writer.getSheet();
        for (int index = 0; index < columnNum; index++) {
            sheet.autoSizeColumn(index);
            int colWidth = sheet.getColumnWidth(index) * 2;
            if (colWidth < 255 * 256) {
                sheet.setColumnWidth(index, Math.max(colWidth, 3000));
            } else {
                sheet.setColumnWidth(index, 3000);
            }
        }
    }

    /**
     * 处理表头
     *
     * @param headerClass bean实体
     * @param <T>         泛型
     */
    private static <T> void handleHeader(Class<T> headerClass) {
        Field[] fields = ReflectUtil.getFields(headerClass);
        for (Field field : fields) {
            ExcelHead annotation = field.getAnnotation(ExcelHead.class);
            boolean isHide = Objects.nonNull(annotation);
            if (isHide) {
                CellStyle style = writer.getStyleSet().getHeadCellStyle();
                style.setFillPattern(FillPatternType.NO_FILL);
                writer.addHeaderAlias(field.getName(), annotation.writer());
                columnNum++;
            }

        }
    }


    /**
     * @param data       数据
     * @param exportName 导出文件名称
     * @param response   response
     * @param <T>        泛型
     */
    private static <T> void write(String exportName, List<T> data, HttpServletResponse response) {
        if (ObjectUtil.isNotEmpty(data)) {
            writer.write(data, true);
            handleColumnWidth();
        }
        ServletOutputStream out = null;
        try {
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(exportName + System.currentTimeMillis(), "UTF-8") + ".xlsx");
            out = response.getOutputStream();
            writer.flush(out, true);
        } catch (IOException e) {
            throw new IllegalArgumentException("导出失败！");
        } finally {
            writer.close();
            IoUtil.close(out);
        }
    }

    /**
     * 处理宽度自适应
     */
    private static void handleColumnWidth() {
        Sheet sheet = writer.getSheet();
        for (int index = 0; index < columnNum; index++) {
            sheet.autoSizeColumn(index);
            sheet.setColumnWidth(index, sheet.getColumnWidth(0) * 17 / 10);
        }
        writer.autoSizeColumnAll();
    }

    /**
     * 输出到磁盘
     *
     * @param data 数据
     * @param url  导出文件位置
     * @param <T>  泛型
     */
    private static <T> void writeToDesk(List<T> data, String url) {
        if (ObjectUtil.isNotEmpty(data)) {
            writer.write(data, true);
            handleColumnWidth();
        }
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(url);
            writer.flush(fileOutputStream);
        } catch (IOException e) {
            throw new IllegalArgumentException("导出到磁盘失败！");
        } finally {
            writer.close();
            IoUtil.close(fileOutputStream);
        }
    }


    /**
     * @param writer
     * @param x           单元格x轴坐标
     * @param y           单元格y轴坐标
     * @param pictureData 图片二进制数据
     * @param picType     图片类型，对应poi中Workbook类中的图片类型2-7变量
     */
    public static ExcelWriter writePic(ExcelWriter writer, int x, int y, byte[] pictureData, int picType) {
        Sheet sheet = writer.getSheet();
        Drawing drawingPatriarch = sheet.createDrawingPatriarch();
        //设置图片在单元格位置
        /* dx1:图片左边界距离单元格左边框像素值,
         * dy1:图片上边界距离单元格上边框像素值,
         * dx2:图片右边界距离单元格右边框像素值（负数）,
         * dy2:图片下边界距离单元格下边框像素值（负数）,
         * col1:列下标（0开始），
         * row1:行下标（0开始），
         * col2:列下标（1开始），
         * row2:行下标（1开始）。*/
        ClientAnchor anchor = drawingPatriarch.createAnchor(50000,50000, -50000, -50000, x, y, x + 1, y + 1);
        //随单元格改变位置和大小
        anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
        //添加图片
        int pictureIndex = sheet.getWorkbook().addPicture(pictureData, picType);
        drawingPatriarch.createPicture(anchor, pictureIndex);
        return writer;
    }

    /**
     * excel内容到java Bean中 配合ReadExcelHead注解
     * @param excelInputStream
     * @param entityClass
     */
    public static <T> List<T> readToBean(InputStream excelInputStream, Class<T> entityClass) {
        return readToBean( excelInputStream,entityClass, 0);
    }

    /**
     * excel内容到java Bean中 配合ReadExcelHead注解
     * @param excelInputStream
     * @param entityClass
     * @headRowIndex 表头所在行【范围大于等于0】
     */
    public static <T> List<T> readToBean(InputStream excelInputStream, Class<T> entityClass, int headRowIndex) {
        headRowIndex = Math.max(headRowIndex, 0);
        Field[] fields = entityClass.getDeclaredFields();
        Map<String, String> headMap = new LinkedHashMap<>();
        for (Field field : fields)
            if (field.isAnnotationPresent(ExcelHead.class)) {
                ExcelHead annotation = field.getAnnotation(ExcelHead.class);
                String read = annotation.read();
                String name = field.getName();
                headMap.put(name, read);
            }
        ExcelReader reader = null;

        try {
            reader = ExcelUtil.getReader(excelInputStream, 0);
            List<List<Object>> read = reader.read(0);
            Assert.isTrue(ObjectUtil.isNotEmpty(read), "未获取到数据");
            List<Object> heads = read.get(headRowIndex);
            int headSize = headMap.size();
            if (headSize == 0)
                throw new IllegalArgumentException("检查传入实体内部属性使用ReadExcelHead注解");

            Map<String, Integer> headIndexMap = new HashMap<>();
            headMap.forEach((k, v) -> {
                int index = heads.indexOf(v);
                if (index == -1) {
                    throw new IllegalArgumentException(String.format("excel表头应包含%s", headMap.values().stream().collect(Collectors.joining("、"))));
                } else {
                    headIndexMap.put(k, index);
                }
            });

            List<T> beans = new ArrayList<>();
            for (int i = (headRowIndex + 1); i < read.size(); i++) {
                Properties bean = new Properties();
                List<Object> data = read.get(i);
                headIndexMap.forEach((k, v) -> {
                    String excelData = String.valueOf(data.get(v));
                    bean.setProperty(k, excelData);
                });
                beans.add(BeanUtil.toBean(bean, entityClass));
            }
            return beans;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null)
                    reader.close();
                if (excelInputStream != null) {
                    excelInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        throw new IllegalArgumentException("读取excel信息异常");
    }

}
